import ctypes
import os
from enum import Enum
from multiprocessing import Process, Manager
from threading import Lock
from com.struct_format import *


class TransactionType(Enum):
    edit = 1
    append = 2
    insert = 4


class HeadTagEntity:
    def __init__(self, init=None):
        self.max_val = 0
        self.min_val = 0
        self.top_val = 0
        self.count_val = 0
        if init is not None:
            self.max_val, self.min_val, self.top_val, self.count_val = init

    def get_data(self):
        return self.max_val, self.min_val, self.top_val, self.count_val


class IndexCollectOpt:
    def __init__(self, mem_cls, file_id):
        self.mem_cls = mem_cls
        self.file_id = file_id

    def read_back(self, find, start, last):
        self.mem_cls.readIndexCollectBack(self.file_id, find, start, last)

    def read_front(self, find, start, last):
        self.mem_cls.readIndexCollectFront(self.file_id, find, start, last)

    def has_index(self, find, start):
        self.mem_cls.hasIndexCollect(self.file_id, find, start)


class MemoryFileItem:
    def __init__(self, mem_file_cls, group_name=None, file_num=None, shift=0):
        self.inner_lock = Lock()
        self.mem_cls = mem_file_cls
        self.file_id = 0
        self.append_pos = 0
        self.shift = shift
        self.file_num = 0
        self.file_name = None
        self.group_name = group_name
        self.group_top = 0
        self.file_num = file_num

    def get_append_pos(self):
        return self.append_pos

    def create(self, file_name, size=65535):
        self.file_name = file_name
        self.file_id = self.mem_cls.createMemory(file_name, size)
        self.append_pos = self.tell()
        return self.file_id

    def write(self, dest, start):
        with self.inner_lock:
            self.mem_cls.writeMemory(self.file_id, dest, start, len(dest))

    def append(self, dest):
        with self.inner_lock:
            str_len = len(dest)
            start = self.mem_cls.appendMemory(self.file_id, dest, str_len)
            self.append_pos += str_len
            return self.shift + start, self.shift + self.append_pos

    def read(self, start, size):
        val = self.mem_cls.readMemory(self.file_id, start, size)
        read_val = bytes(size)
        ctypes.memmove(read_val, val, size)
        return read_val

    def read_byte(self, start):
        return self.mem_cls.readByte(self.file_id, start)

    def flush(self):
        self.mem_cls.flushMemory(self.file_id)

    def close(self):
        self.mem_cls.closeMemory(self.file_id)

    def small_endian(self):
        return self.mem_cls.smallEndian()

    def tell(self):
        return self.mem_cls.tell(self.file_id)

    def seek(self, pos):
        self.mem_cls.seek(self.file_id, pos)

    def read_head(self):
        return self.mem_cls.readHeadValue()

    def read_next(self, find, start):
        return self.mem_cls.readNext(self.file_id, find, start)


class MemoryFileOpt:
    _instance_lock = Lock()
    __inner_lock__ = None
    __transaction__ = None

    @staticmethod
    def create_mem():
        if not hasattr(MemoryFileOpt, "__mem_file__"):
            with MemoryFileOpt._instance_lock:
                if not hasattr(MemoryFileOpt, "__mem_file__"):
                    path, name = os.path.split(__file__)
                    MemoryFileOpt.__mem_file__ = ctypes.cdll.LoadLibrary("%s/dll/memorydata.so" % path)
                    MemoryFileOpt.__inner_lock__ = Lock()
        return MemoryFileOpt.__mem_file__

    @staticmethod
    def create(file_name, size, shift, file_num=None):
        mem = MemoryFileOpt.create_mem()
        mem_file = MemoryFileItem(mem_file_cls=mem, shift=shift)
        mem_file.create(file_name=file_name, size=size)
        mem_file.file_num = file_num
        return mem_file

    @staticmethod
    def small_endian():
        mem = MemoryFileOpt.create_mem()
        return mem.smallEndian()

    @staticmethod
    def read_bytes(char_p, size):
        char_val = bytes(size)
        ctypes.memmove(char_val, char_p, size)
        return char_val

    @staticmethod
    def file_count(group_name):
        mem = MemoryFileOpt.create_mem()
        return mem.fileCount(group_name)

    @staticmethod
    def set_value_index_len(length):
        mem = MemoryFileOpt.create_mem()
        return mem.setValueIndexBaseLen(length)

    @staticmethod
    def find_value_index(group_name, start, i_end, field_name, size, base_length):
        mem = MemoryFileOpt.create_mem()
        char_p = mem.findValueIndex(group_name, start, i_end, field_name, base_length)
        read_val = bytes(size)
        ctypes.memmove(read_val, char_p, size)
        return read_val

    @staticmethod
    def group_init_data(group_name, path, size, index_len):
        mem = MemoryFileOpt.create_mem()
        with MemoryFileOpt.__inner_lock__:
            rtn = mem.initData(group_name, path, size, index_len)
        return rtn

    @staticmethod
    def append_group(group_name, dest, tran=True):
        mem = MemoryFileOpt.create_mem()
        with MemoryFileOpt.__inner_lock__:
            if tran and MemoryFileOpt.__transaction__ is not None:
                old_pos, new_pos = MemoryFileOpt.__transaction__.get_group(group_name).append(dest)
            else:
                str_len = len(dest)
                char_p = mem.groupAppend(group_name, dest, str_len)
                read_val = bytes(file_inf_size)
                ctypes.memmove(read_val, char_p, file_inf_size)
                old_pos, new_pos = struct.unpack(file_inf_struct, read_val)
            return old_pos, new_pos

    @staticmethod
    def read_group(group_name, start, size):
        if MemoryFileOpt.__transaction__ is not None and MemoryFileOpt.__transaction__.has_data(group_name, start):
            return MemoryFileOpt.__transaction__.get_group(group_name).read(start)
        mem = MemoryFileOpt.create_mem()
        val = mem.readGroup(group_name, start, size)
        read_val = bytes(size)
        ctypes.memmove(read_val, val, size)
        return read_val

    @staticmethod
    def write_group(group_name, dest, start, tran=True):
        mem = MemoryFileOpt.create_mem()
        with MemoryFileOpt.__inner_lock__:
            if tran and MemoryFileOpt.__transaction__ is not None:
                MemoryFileOpt.__transaction__.get_group(group_name).write(start, dest)
            else:
                mem.writeGroup(group_name, dest, start, len(dest))

    @staticmethod
    def append_collect(group_name, dest, tran=True):
        mem = MemoryFileOpt.create_mem()
        with MemoryFileOpt.__inner_lock__:
            if tran and MemoryFileOpt.__transaction__ is not None:
                old_pos, new_pos = MemoryFileOpt.__transaction__.get_group(group_name).append_collect(dest)
            else:
                char_p = mem.findWriteGroup(group_name, dest)
                read_val = bytes(file_inf_size)
                ctypes.memmove(read_val, char_p, file_inf_size)
                old_pos, new_pos = struct.unpack(file_inf_struct, read_val)
            return old_pos, new_pos

    @staticmethod
    def group_tell(group_name):
        mem = MemoryFileOpt.create_mem()
        return mem.groupTell(group_name)

    # @staticmethod
    # def append_split(group_name, data, index_len, size):
    #     mem = MemoryFileOpt.create_mem()
    #     char_p = mem.appendSplit(group_name, data, index_len, size)
    #     read_val = bytes(file_inf_size)
    #     ctypes.memmove(read_val, char_p, file_inf_size)
    #     old_pos, new_pos = struct.unpack(file_inf_struct, read_val)
    #     return old_pos, new_pos

    # @staticmethod
    # def append_data_value(group_name, field_key, data, pos, base_index_len):
    #     mem = MemoryFileOpt.create_mem()
    #     return mem.appendIndexDataValue(group_name, field_key, data, pos, base_index_len)

    @staticmethod
    def group_head_tag(group_name):
        mem = MemoryFileOpt.create_mem()
        char_p = mem.groupHeadTag(group_name)
        read_val = bytes(group_head_size)
        ctypes.memmove(read_val, char_p, group_head_size)
        max_val, min_val, top_val, count_val = struct.unpack(group_head_struct, read_val)
        return max_val, min_val, top_val, count_val

    @staticmethod
    def find_collect_next(group_name, front, back, field_key, tp):
        mem = MemoryFileOpt.create_mem()
        char_p = mem.findCollectNextValue(group_name, front, back, field_key, tp)
        if char_p > 0:
            read_val = bytes(collect_fmt_size)
            ctypes.memmove(read_val, char_p, collect_fmt_size)
            status, s_front, s_back, str_val, i_start, i_end, d_start, d_end, pos = struct.unpack(collect_fmt, read_val)
            return status, s_front, s_back, str_val, i_start, i_end, d_start, d_end, pos
        return 0, 0, 0, 0, 0, 0, 0, 0, 0

    @staticmethod
    def group_seek(group_name, pos):
        mem = MemoryFileOpt.create_mem()
        mem.groupSeek(group_name, pos)

    @staticmethod
    def close():
        mem = MemoryFileOpt.create_mem()
        mem.close_mem()

    @staticmethod
    def print_group(group_name):
        mem = MemoryFileOpt.create_mem()
        mem.printGroup(group_name)

    @staticmethod
    def load_group_file(group_name, file_name):
        mem = MemoryFileOpt.create_mem()
        mem.loadGroupFile(group_name, file_name)

    @staticmethod
    def lock_file(file_name):
        mem = MemoryFileOpt.create_mem()
        fd = mem.fileLock(file_name)
        return fd

    @staticmethod
    def unlock_file(fd):
        mem = MemoryFileOpt.create_mem()
        rtn = mem.fileUnlock(fd)
        return rtn > 0

    @staticmethod
    def commit():
        if MemoryFileOpt.__transaction__ is not None:
            MemoryFileOpt.__transaction__.commit()

    @staticmethod
    def rollback():
        if MemoryFileOpt.__transaction__ is not None:
            MemoryFileOpt.__transaction__.rollback()

    @staticmethod
    def delete_group_item(group_name, pos):
        mem = MemoryFileOpt.create_mem()
        mem.deleteGroupData(group_name, pos)


class TransactionDataEntity:
    def __init__(self, opt, pos, data_bytes):
        self.opt = opt
        self.pos = pos
        self.data = data_bytes


class TransactionGroup:
    def __init__(self, group_name, encode="utf-8"):
        self.group_name = str(group_name, encode)
        self.group_name_bytes = group_name
        self.fd = self.__lock_file__()
        self.position = MemoryFileOpt.group_tell(self.group_name_bytes)
        self.head_tag = HeadTagEntity(MemoryFileOpt.group_head_tag(self.group_name_bytes))
        self.transaction_data = list()
        self.transaction_data_dict = dict()

    def has_data(self, pos):
        return pos in self.transaction_data_dict

    def write(self, pos, data):
        data_en = TransactionDataEntity(TransactionType.edit.value, pos, data)
        self.transaction_data.append(data_en)
        self.transaction_data_dict[pos] = data_en

    def read(self, pos):
        if pos in self.transaction_data_dict:
            return self.transaction_data_dict[pos]
        return None

    def append(self, data):
        old_pos = self.position
        data_en = TransactionDataEntity(TransactionType.append.value, 0, data)
        self.transaction_data.append(data_en)
        self.position += len(data)
        self.head_tag.count_val += 1
        return old_pos, self.position

    def append_collect(self, data):
        old_pos = self.position
        data_en = TransactionDataEntity(TransactionType.insert.value, 0, data)
        self.transaction_data.append(data_en)
        self.position += len(data)
        return old_pos, self.position

    def commit(self, safe=True):
        file_name = None
        if safe:
            file_name = self.__commit_file__()
        self.__commit__()
        if file_name is not None and os.path.exists(file_name):
            os.remove(file_name)
        self.transaction_data.clear()
        self.__unlock_file__()

    def __commit__(self):
        for item in self.transaction_data:
            if TransactionType.edit.value == item.opt:
                self.__edit__(item)
            elif TransactionType.append.value == item.opt:
                self.__append__(item)
            elif TransactionType.insert.value == item.opt:
                self.__insert__(item)

    def __edit__(self, item):
        MemoryFileOpt.write_group(self.group_name_bytes, item.data, item.pos, False)

    def __append__(self, item):
        MemoryFileOpt.append_group(self.group_name_bytes, item.data, False)

    def __insert__(self, item):
        MemoryFileOpt.append_collect(self.group_name_bytes, item.data, False)

    def __commit_file__(self):
        tmp_file_name = "tr_%d.tran.tmp" % os.getpid()
        file_name = "tr_%d.tran" % os.getpid()
        tmp_dir_str = os.path.join(self.group_name, tmp_file_name)
        dir_str = os.path.join(self.group_name, file_name)
        with open(tmp_dir_str, "ab+") as fs:
            for item in self.transaction_data:
                opt = struct.pack(safe_fmt, item.opt, len(self.group_name_bytes), len(item.data), item.pos)
                fs.write(opt)
                fs.write(self.group_name_bytes)
                fs.write(item.data)
            fs.flush()
        os.rename(tmp_dir_str, dir_str)
        return dir_str

    def __lock_file__(self):
        fd = -1
        lock_file_name = os.path.join(self.group_name, "group.lock")
        while fd < 0:
            fd = MemoryFileOpt.lock_file(lock_file_name.encode("utf-8"))
        return fd

    def __unlock_file__(self):
        MemoryFileOpt.unlock_file(self.fd)
        lock_file_name = os.path.join(self.group_name, "group.lock")
        if os.path.exists(lock_file_name):
            os.remove(lock_file_name)


class TransactionData:
    def __init__(self):
        self.tran_group = dict()

    def get_group(self, group_name):
        if group_name not in self.tran_group:
            self.tran_group[group_name] = TransactionGroup(group_name)
        return self.tran_group[group_name]

    def has_data(self, group_name, pos):
        return group_name in self.tran_group and self.tran_group[group_name].has_data(pos)

    def commit(self, safe=True):
        for k, v in self.tran_group.items():
            v.commit(safe)

    def rollback(self):
        self.tran_group.clear()

    @staticmethod
    def load_rollback_file(path):
        dirs = os.listdir(path)
        for d in dirs:
            file_name = os.path.join(path, d)
            if os.path.isfile(file_name):
                if file_name.endswith(".tran"):
                    with open(file_name, "ab") as fs:
                        file_size = fs.tell()
                        fs.seek(0)
                        while fs.tell() < file_size:
                            head_bytes = fs.read(safe_fmt_size)
                            opt, group_size, data_size, pos = struct.unpack(safe_fmt, head_bytes)
                            group = fs.read(group_size)
                            data = fs.read(data_size)
                            if TransactionType.edit.value == opt:
                                MemoryFileOpt.write_group(group, data, pos, False)
                    os.remove(file_name)


class TransactionBase:
    def __init__(self, callback, block=True):
        self.callback = callback
        self.manager = Manager()
        self.block = block

        if MemoryFileOpt.__transaction__ is None:
            process = Process(target=self.__run__)
            process.start()
            process.join()
        else:
            self.callback()

    def __enter__(self):
        return self

    def __run__(self):
        MemoryFileOpt.__transaction__ = TransactionData()
        self.callback()
